﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Collections.Generic;
using System.Text;
using System.Runtime;
using System.Runtime.Serialization;
using System.Threading;

namespace PickGold.Util.Net
{
	/// <summary>
	/// 
	/// </summary>
	public class IcmpPing
	{
		const int HEAD = 8;

		/// <summary>
		/// 类型
		/// </summary>
		private byte _Type;
		/// <summary>
		/// 代码
		/// </summary>
		private byte _Code;
		/// <summary>
		/// 校验和
		/// </summary>
		private ushort _Check;
		/// <summary>
		/// 识别符
		/// </summary>
		private ushort _Identifier;
		/// <summary>
		/// 序列号
		/// </summary>
		private ushort _Sequence;
		/// <summary>
		/// 选项数据
		/// </summary>
		private int _Size;

		private string _Host;

		private string _Local;

		private string _Remote;

		private byte[] _Data;

		private int _Count;

		private int _Ticks;

		private byte _TTL;

		private Exception _Error;

		private Socket _Socket;

		/// <summary>
		/// 
		/// </summary>
		public IcmpPing() : this(null, 0, 0, 0, byte.MaxValue, 0, 0, 0) { }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="size"></param>
		public IcmpPing(int size) : this(null, 0, 0, 0, byte.MaxValue, 0, 0, size) { }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="host"></param>
		public IcmpPing(string host) : this(host, 0, 0, 0, byte.MaxValue, 0, 0, 0) { }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="host"></param>
		/// <param name="count"></param>
		public IcmpPing(string host, int count) : this(host, 0, 0, 0, byte.MaxValue, 0, count, 0) { }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="host"></param>
		/// <param name="count"></param>
		/// <param name="size"></param>
		public IcmpPing(string host, int count, int size) : this(host, 0, 0, 0, byte.MaxValue, 0, count, size) { }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="host"></param>
		/// <param name="type"></param>
		/// <param name="code"></param>
		/// <param name="check"></param>
		/// <param name="identifier"></param>
		/// <param name="sequence"></param>
		/// <param name="count"></param>
		/// <param name="size"></param>
		protected IcmpPing(string host, byte type, byte code, ushort check, ushort identifier, ushort sequence, int count, int size)
		{
			this._Host = host;
			this._Type = type == 0 ? (byte)8 : type;
			this._Code = code;
			this._Check = check;
			this._Identifier = identifier;
			this._Sequence = sequence;
			this._Count = count;
			this._Size = size <= 0 ? 4 : size % 2 == 0 ? size : size + 1;
		}

		/// <summary>
		/// 
		/// </summary>
		public int Count
		{
			get
			{
				return this._Count;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public string Local
		{
			get
			{
				return this._Local;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public string Remote
		{
			get
			{
				return this._Remote;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public int Ticks
		{
			get
			{
				return this._Ticks;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public byte[] Data
		{
			get
			{
				return this._Data;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public int TTL
		{
			get
			{
				return this._TTL;
			}
		}

		/// <summary>
		/// 初始化ICMP报文
		/// </summary>
		/// <returns></returns>
		public byte[] GetPacket()
		{
			var bs = new byte[this._Size + 8];
			var i = (this._Code << 8) + this._Type;
			i += this._Identifier;
			i += this._Sequence;
			for (var ii = 0; ii < this._Size; )
			{
				bs[0] = bs[HEAD + ii++] = (byte)(ii + HEAD);//由于选项数据在此命令中并不重要，所以你可以改换任何你喜欢的字符 
				bs[1] = bs[HEAD + ii++] = (byte)(ii + HEAD);
				i = i + bs[0] + (bs[1] << 8);//将两个byte转化为一个uint16
			}
			i = (i >> 16) + (i & 0xFFFF);
			i += (i >> 16);
			this._Check = (ushort)(~i);//将校验和保存至报文里
			i = 0;
			bs[i++] = this._Type;
			bs[i++] = this._Code;
			bs[i++] = (byte)this._Check;
			bs[i++] = (byte)(this._Check >> 8);
			bs[i++] = (byte)this._Identifier;
			bs[i++] = (byte)(this._Identifier >> 8);
			bs[i++] = (byte)this._Sequence;
			bs[i++] = (byte)(this._Sequence >> 8);
			i += this._Size;
			return bs;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public void Ping(Action<IcmpPing> cb)
		{
			var h = new Func<IcmpPing, bool>(p => { cb(p); return true; });
			this.Ping(h);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public void Ping(Func<IcmpPing, bool> cb)
		{
			var ip = IPAddress.Loopback;
			if (!string.IsNullOrWhiteSpace(this._Host))
			{
				try
				{
					var he = Dns.GetHostEntry(this._Host);//解析主机ip入口
					ip = he.AddressList[0];
				}
				catch (Exception x)
				{
					this._Error = x;//new Exception("解析主机名错误。", x);
					this._Count = 0;
					this.End(cb);
					return;
				}
			}

			var remote = (EndPoint)new IPEndPoint(ip, 0);// 取主机的任意端口
			if (this._Socket == null)
			{
				this._Socket = new Socket(ip.AddressFamily, SocketType.Raw, ProtocolType.Icmp);
				this._Socket.ReceiveTimeout = 1000 * 10;
			}
			var start = Environment.TickCount;//系统计时开始
			this._Ticks = this._Identifier + 1;
			this._Identifier = (ushort)this._Ticks;
			this._Data = this.GetPacket();
			if (this._Socket.SendTo(this._Data, this._Data.Length, SocketFlags.None, remote) == -1)//发送数据包
			{
				this._Error = new Exception("无法传送报文！");
				this._Ticks = 0;
				this.End(cb);
				return;
			}

			var bs = new byte[this._Data.Length + byte.MaxValue]; //接收数据
			this._Ticks = Environment.TickCount;
			this._Socket.BeginReceiveFrom(bs, 0, bs.Length, SocketFlags.None, ref remote, this.EndReceive, new object[] { cb, bs, this._Socket, ip, });
		}

		private void EndReceive(IAsyncResult result)
		{
			if (Thread.CurrentThread.IsThreadPoolThread && Fiber.QueueUserAsyncCallback(this.EndReceive, result))
				return;

			var i = 0;
			var os = result.AsyncState as object[];
			var cb = os[i++] as Func<IcmpPing, bool>;
			var bs = os[i++] as byte[];
			var sk = os[i++] as Socket;
			var ip = os[i++] as IPAddress;
			try
			{
				var ep = this._Socket.LocalEndPoint;
				i = sk.EndReceiveFrom(result, ref ep);
			}
			catch (Exception x)
			{
				this._Error = x;
				this._Ticks = Environment.TickCount - this._Ticks;
				this.End(cb);
				return;
			}

			if (i == -1)
			{
				this._Error = new Exception("主机没有响应！");
				this.End(cb);
				return;
			}

			this._Data = new byte[i];
			Buffer.BlockCopy(bs, 0, this._Data, 0, i);
			this._Ticks = Environment.TickCount - this._Ticks;//得到发送报文到接收报文之间花费的时间
			if (ip.AddressFamily == AddressFamily.InterNetwork)
			{
				if (this._Identifier == (bs[25] << 8) + bs[24])
				{
					this._Remote = bs[12] + "." + bs[13] + "." + bs[14] + "." + bs[15];
					this._Local = bs[16] + "." + bs[17] + "." + bs[18] + "." + bs[19];
					this._TTL = bs[8];//Trace.WriteLine("Reply From " + this._Remote + "(" + this._Local + ") Received " + ii + " Bytes TTL " + this._TTL + " In " + this._Ticks + "ms");//得到发送报文到接收报文之间花费的时间
				}
				else
				{
					this._Error = new Exception("收到意外数据！");
				}
			}
			else
			{
				if (this._Identifier == (bs[5] << 8) + bs[4])
				{
					this._Remote = bs[12] + "." + bs[13] + "." + bs[14] + "." + bs[15];
					this._Local = bs[16] + "." + bs[17] + "." + bs[18] + "." + bs[19];
					this._TTL = bs[8];
				}
				else
				{
					this._Error = new Exception("收到意外数据！");
				}
			}
			this.End(cb);
		}

		private void End(Func<IcmpPing, bool> cb)
		{
			var i = Environment.TickCount;
			if (!cb(this))
				this._Count = 0;
			i = Environment.TickCount - i + this._Ticks;
			this._Ticks = 0;
			if (this._Count > 0)
			{
				if (i < 1000)
					Thread.Sleep(1000 - this._Ticks);
				this._Count--;
				this.Ping(cb);
				return;
			}

			this._Socket.Close();//关闭套接字
			this._Socket = null;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public static Ping CreatePing()
		{
			return new Ping();
		}
	}
}
